package com.mlming.simpledb.backend.tm;

import com.mlming.simpledb.backend.utils.Panic;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import com.mlming.simpledb.backend.utils.Parser;
import com.mlming.simpledb.common.Error;

public class TransactionManagerImpl implements TransactionManager {
    // XID文件格式 相关参数:
    static final int XID_HEADER_LENGTH = 8; // 文件头共8个字节, 用于记录当前XID文件管理的事务数
    private static final int XID_FIELD_SIZE = 1; // 每一个事务的状态用一个字节长度来记录

    // 事务的三种状态: 一个字节表示:
    private static final byte FIELD_TRAN_ACTIVE = 0; // active
    private static final byte FIELD_TRAN_COMMITTED = 1; // committed
    private static final byte FIELD_TRAN_ABORTED = 2; // aborted

    // 超级事务的XID:
    public static final long SUPER_XID = 0;

    // XID文件的后缀名
    public static final String XID_SUFFIX = ".xid";

    // 实现功能需要的成员变量:
    private RandomAccessFile file; // 用于代表XID文件 RandomAccessFile比较灵活
    private FileChannel fc; // 用于读写XID文件 FileChannel性能高, 有多线程并发保障
    private long xidCounter; // 记录本XID文件的文件头所表示的事务数量
    private Lock counterLock; // 用于加锁防止多线程安全问题

    // 构造函数:
    // 要传递 XID文件 以及 对应的FileChannel进来
    // 同时要去检查 XID文件是否合法
    public TransactionManagerImpl(RandomAccessFile file, FileChannel fc) {
        this.file = file;
        this.fc = fc;
        counterLock = new ReentrantLock();
        // 检查XID文件是否合法:
        checkXIDValid();
    }
    // 检查XID文件是否合法:
    // 读取XID文件前8个字节, 转化为long类型, 即: 当前XID文件管理的事务数量
    // 因此, 就可以计算出 按照XID规则的应有长度: 文件头长度 + 事务数量*各个事务表状态的长度
    // 以此值与文件实际长度进行判断, 如果不相等就说明不合法
    private void checkXIDValid() {
        // 1) 获取文件原长度:
        long fileLen = 0;
        try {
            fileLen = file.length();
        } catch (IOException e) {
            Panic.panic(Error.BadXIDFileException);
        }
        // 如果长度比文件头规定长度还小, 必然不合法
        if(fileLen < XID_HEADER_LENGTH) {
            Panic.panic(Error.BadXIDFileException);
        }
        // 2) 读取XID文件的前8个字节的值, 获得当前XID文件管理的事务数量
        ByteBuffer buf = ByteBuffer.allocate(XID_HEADER_LENGTH); // 创建一个8字节大小的缓冲区
        try {
            fc.position(0); // fc定位到XID文件的开始处
            fc.read(buf); // 读入8个字节到buf中
        } catch (IOException e) {
            Panic.panic(e);
        }
        this.xidCounter = Parser.parseLong(buf.array()); // 通过buf得到对应的事务数量
        // 3) 计算逻辑上XID文件应该的长度, 与当前XID文件的长度进行比较, 不相等说明不合法
        // 因为getXidPosition求的是一个xid事务其在XID文件中的偏移量,
        // 此时xidCount代表当前最后一个事务的xid,
        // 其+1代表下一个要被创建的事务的xid,该未来新事务的偏移量, 正是当前XID文件的末尾
        long offset = getXidPosition(xidCounter + 1);
        if(offset != fileLen) Panic.panic(Error.BadXIDFileException);
    }

    // 根据事务xid取得该事务的状态在xid文件中对应的位置
    // 因为超级事务(xid=0)不归XID管理, 所以xid要-1 再乘每个事务表状态占的长度
    private long getXidPosition(long xid) {
        return XID_HEADER_LENGTH + (xid - 1) * XID_FIELD_SIZE;
    }

    @Override
    // 开启一个事务,并返回xid
    // 首先我们知道, 根据XID文件规则, 当前事务数量xidCount的值 就是 当前最后一个事务的xid
    // 所以下一个,即现在要被创建的事务的xid, 就是xidCount + 1
    // 所以: begin的逻辑是:
    // 1) 根据xidCount + 1计算出当前要被创建的事务xid
    // 2) 该事务状态置为active
    // 3) 把该事务的状态追加到XID文件末尾
    // 4) XID文件的文件头的值加一(事务数量 + 1)
    // **因为此时要对XID文件进行写操作了, 所以必须加锁**
    public long begin() {
        counterLock.lock();
        try {
            // 1) 根据xidCount + 1计算出当前要被创建的事务xid
            long curXid = this.xidCounter + 1;
            // 2) 该事务状态置为active 3) 把该事务的状态追加到XID文件末尾
            updateXID(curXid,FIELD_TRAN_ACTIVE);
            // 4) XID文件的文件头的值加一(事务数量 + 1)
            incrXIDCounter();
            return curXid;
        } finally {
            counterLock.unlock();
        }
    }

    // 将xid对应的事务的状态修改为status,并写入/覆盖到XID文件中
    private void updateXID(long xid, byte status) {
        // 获得该xid对应的事务在XID文件中所处的位置
        long offset = getXidPosition(xid);
        // 通过status构造一个ByteBuffer缓冲区, 从而往XID文件里面写
        byte[] tmp = new byte[XID_FIELD_SIZE]; // 每一个事务一个字节来表示其状态
        tmp[0] = status;
        ByteBuffer buf = ByteBuffer.wrap(tmp);
        // 往XID文件对应位置写入
        try {
            fc.position(offset); // 先定位到偏移量处
            fc.write(buf); // 往XID文件里写
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            // 这里的所有文件操作，在执行后都需要立刻刷入文件中，防止在崩溃后文件丢失数据，
            // fileChannel 的 force() 方法，强制同步缓存内容到文件中，类似于 BIO 中的 flush() 方法。
            // force 方法的参数是一个布尔，表示是否同步文件的元数据（例如最后修改时间等）。
            fc.force(false);
        } catch (IOException e) {
            Panic.panic(e);
        }
    }

    // XID文件的文件头的值加一(事务数量 + 1)
    private void incrXIDCounter() {
        // 当前xidCounter + 1
        xidCounter ++;
        // 通过xidCount转化为的byte数组构造ByteBuffer缓冲区
        ByteBuffer buf = ByteBuffer.wrap(Parser.long2Byte(xidCounter));
        // 写入XID文件的文件头
        try {
            fc.position(0); // 定位到开始处(文件头开始处)
            fc.write(buf); // 写入新值
        } catch (IOException e) {
            Panic.panic(e);
        }
        try {
            fc.force(false);
        } catch (IOException e) {
            Panic.panic(e);
        }
    }

    @Override
    // 指定提交一个事务
    public void commit(long xid) {
        // 直接调用updateXID更改该xid对应的事务状态为committed,并覆盖到XID文件对应位置即可
        updateXID(xid,FIELD_TRAN_COMMITTED);
    }

    @Override
    // 指定回滚一个事务
    public void abort(long xid) {
        // 直接调用updateXID更改该xid对应的事务状态为abort,并覆盖到XID文件对应位置即可
        updateXID(xid,FIELD_TRAN_ABORTED);
    }

    @Override
    public boolean isActive(long xid) {
        // 如果是超级事务, 其不归XID文件管, 且其状态必然是committed
        if(xid == SUPER_XID) return false;
        return checkXID(xid,FIELD_TRAN_ACTIVE);
    }

    @Override
    public boolean isCommitted(long xid) {
        // 如果是超级事务, 其不归XID文件管, 且其状态必然是committed
        if(xid == SUPER_XID) return true;
        return checkXID(xid,FIELD_TRAN_COMMITTED);
    }

    @Override
    public boolean isAborted(long xid) {
        // 如果是超级事务, 其不归XID文件管, 且其状态必然是committed
        if(xid == SUPER_XID) return false;
        return checkXID(xid,FIELD_TRAN_ABORTED);
    }

    // 获得xid对应事务在XID文件中记录的状态
    private byte getFieldTran(long xid) {
        // 获取位置
        long offset = getXidPosition(xid);
        // 构造一个1字节大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(XID_FIELD_SIZE);
        try {
            fc.position(offset);
            fc.read(buf);
        } catch (IOException e) {
            Panic.panic(e);
        }
        return buf.array()[0];
    }

    // 检查xid对应事务在XID文件中记录的状态 是否等于 status
    private boolean checkXID(long xid,byte status) {
        return getFieldTran(xid) == status;
    }

    @Override
    // 关闭TM
    // 因为TM其实就是通过XID文件进行操作, 所以关闭TM, 就是停止操作XID文件即可
    public void close() {
        try {
            fc.close();
            file.close();
        } catch (IOException e) {
            Panic.panic(e);
        }
    }
}
